﻿using System;
using System.Collections.Generic;
using System.Text;

using RaisingStudio.Data.Common.Factories;
using RaisingStudio.Data.Common.Managers;
using System.Data;

namespace RaisingStudio.Data.Common
{    
    /// <summary>
    /// Command converter.
    /// </summary>
    public class CommandConverter
    {
        /// <summary>
        /// Convert the common command into actual dbcommand.
        /// </summary>
        /// <param name="commonCommand">common command.</param>
        /// <param name="connection">connection.</param>
        /// <returns>actual dbcommand.</returns>
        public static System.Data.IDbCommand Convert(CommonCommand commonCommand, System.Data.IDbConnection connection, DatabaseType databaseType, string providerName)
        {
            //DatabaseType databaseType = ConnectionFactory.GetDatabaseType(connection);
            string commandText = commonCommand.CommandText;

            System.Data.IDbCommand command = connection.CreateCommand();
            // some database don't support that.
            //command.CommandTimeout = commonCommand.CommandTimeout;
            command.CommandType = commonCommand.CommandType;
            // Setup parameters. 
            foreach (CommonParameter commonParameter in commonCommand.Parameters)
            {
                // TODO: ODBC、OLEDB the same parameter name issue.
                string parameterName = ReplaceParameterName(commonCommand.CommandType, commonParameter.ParameterName, commonCommand.ParameterPrefix, databaseType, providerName, ref commandText);

                System.Data.IDbDataParameter parameter = command.CreateParameter();
                parameter.ParameterName = parameterName;
                if (commonParameter.DbType != System.Data.DbType.String)
                {
                    parameter.DbType = commonParameter.DbType;
                    parameter.Size = commonParameter.Size;
                }
                parameter.Direction = commonParameter.Direction;
                parameter.SourceColumn = commonParameter.SourceColumn;
                // TODO: 2009-02-24 mapping.
                /*
                parameter.SourceColumnNullMapping = commonParameter.SourceColumnNullMapping;
                 */
                parameter.SourceVersion = commonParameter.SourceVersion;
                object value = commonParameter.Value;
                if (value == null)
                {
                    parameter.Value = System.DBNull.Value;
                }
                else
                {
                    Type valueType = value.GetType();
                    value = ConvertDataValue(valueType, parameter.DbType, value);
                    parameter.Value = value;
                }

                command.Parameters.Add(parameter);
            }
            command.CommandText = commandText;
            return command;
        }

        /// <summary>
        /// Replace the parameter name in the command text.
        /// </summary>
        /// <param name="commonCommandText">Common command text.</param>
        /// <param name="commonParameterName">Common parameter name.</param>
        /// <param name="parameterName">Replace parameter name.</param>
        /// <returns>Replaced command text.</returns>
        public static string ReplaceParameterName(string commonCommandText, string commonParameterName, string parameterName)
        {
            string commandText = commonCommandText;
            int index = commandText.IndexOf(commonParameterName);
            while (index >= 0)
            {
                int parameterNameLength = 0;
                if (index + commonParameterName.Length < commandText.Length)
                {
                    char followingChar = commandText[index + commonParameterName.Length];
                    if (char.IsLetterOrDigit(followingChar))
                    {
                        parameterNameLength = commonParameterName.Length;
                    }
                    else
                    {
                        commandText = commandText.Remove(index, commonParameterName.Length);
                        commandText = commandText.Insert(index, parameterName);
                        parameterNameLength = parameterName.Length;
                    }
                }
                else
                {
                    commandText = commandText.Remove(index, commonParameterName.Length);
                    commandText = commandText.Insert(index, parameterName);
                    parameterNameLength = parameterName.Length;
                }
                index = commandText.IndexOf(commonParameterName, index + parameterNameLength);
            }
            return commandText;
        }

        public const string SQL_PARAMETER_PREFIX = "@";
        public const string ORACLE_PARAMETER_PREFIX = ":";
        public const string POSITIONALPARAMETER = "?";

        public static string TrimStringStart(string text, string trimText)
        {
            if (text.StartsWith(trimText))
            {
                return text.Remove(0, trimText.Length);
            }
            return text;
        }

        /// <summary>
        /// Replace the parameter name according the database type, include the parameters in the command text.
        /// </summary>
        /// <param name="commonParameterName">Common parameter name.</param>
        /// <param name="databaseType">database type.</param>
        /// <param name="commandText">command text.</param>
        /// <returns>Replacedparameter name.</returns>
        public static string ReplaceParameterName(CommandType commandType, string commonParameterName, string commonParameterPrefix, DatabaseType databaseType, string providerName, ref string commandText)
        {
            switch (databaseType)
            {
                case DatabaseType.SQL:
                case DatabaseType.SQLCE:
                    {
                        // rule: UseParameterPrefixInSql is true, and UseParameterPrefixInParameter is true, ParameterPrefix is "@".
                        string parameterName = commonParameterName;
                        if (commonParameterPrefix != SQL_PARAMETER_PREFIX)
                        {
                            parameterName = string.Format("{0}{1}", SQL_PARAMETER_PREFIX, TrimStringStart(commonParameterName, commonParameterPrefix));
                            if (commandType == CommandType.Text)
                            {
                                commandText = ReplaceParameterName(commandText, commonParameterName, parameterName);
                            }
                        }
                        return parameterName;
                    }
                case DatabaseType.ORACLE:
                    {
                        // rule: UseParameterPrefixInSql is true, and UseParameterPrefixInParameter is false, ParameterPrefix is ":".
                        string parameterName = commonParameterName;
                        if (commonParameterPrefix != ORACLE_PARAMETER_PREFIX)
                        {
                            parameterName = TrimStringStart(commonParameterName, commonParameterPrefix);
                            if (commandType == CommandType.Text)
                            {
                                commandText = ReplaceParameterName(commandText, commonParameterName, string.Format("{0}{1}", ORACLE_PARAMETER_PREFIX, parameterName));
                            }
                        }
                        return parameterName;
                    }
                case DatabaseType.ODBC:
                    {
                        // rule: UsePositionalParameters is true.
                        string parameterName = POSITIONALPARAMETER;
                        if (commandType == CommandType.Text)
                        {
                            commandText = ReplaceParameterName(commandText, commonParameterName, parameterName);
                        }
                        return commonParameterName;
                    }
                case DatabaseType.OLEDB:
                    {
                        // rule: UsePositionalParameters is true.
                        string parameterName = POSITIONALPARAMETER;
                        if (commandType == CommandType.Text)
                        {
                            commandText = ReplaceParameterName(commandText, commonParameterName, parameterName);
                        }
                        return commonParameterName;
                    }
                case DatabaseType.OTHER:
                    {
                        // TODO: providers.
                        string parameterNameInCommandText;
                        string parameterName = ProviderManager.Instance.GetParameterName(providerName, commonParameterName, commonParameterPrefix, out parameterNameInCommandText);
                        if (commandType == CommandType.Text)
                        {
                            commandText = ReplaceParameterName(commandText, commonParameterName, parameterNameInCommandText);
                        }
                        return parameterName;
                    }
                case DatabaseType.SHAREPOINT:
                    {
                        // TODO: sharepoint parameter.
                        string parameterName = POSITIONALPARAMETER;
                        if (commandType == CommandType.Text)
                        {
                            commandText = ReplaceParameterName(commandText, commonParameterName, parameterName);
                        }
                        return commonParameterName;
                    }
                default:
                    {
                        throw (new System.NotSupportedException("Unknown Type."));
                    }
            }
        }

        /// <summary>
        /// Feedback parameter value.
        /// </summary>
        /// <param name="commonCommand">Common Command.</param>
        /// <param name="command">Command.</param>
        public static void FeedbackParameters(ref CommonCommand commonCommand, System.Data.IDbCommand command)
        {
            for (int i = 0; i < command.Parameters.Count; i++)
            {
                System.Data.IDbDataParameter parameter = command.Parameters[i] as System.Data.IDbDataParameter;
                if (parameter.Direction != System.Data.ParameterDirection.Input)
                {
                    commonCommand.Parameters[i].Value = parameter.Value;
                }
            }
        }


        private static object ConvertDataValue(System.Data.DbType dbType, Type propertyType, object value)
        {
            return ConverterManager.ConvertDataValue(dbType, propertyType, value);
        }

        private static object ConvertDataValue(Type propertyType, System.Data.DbType dbType, object value)
        {
            return ConverterManager.ConvertDataValue(propertyType, dbType, value);
        }
    }
}
